﻿#region Copyright (c) 2012-10, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

#if TESTS

using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;


namespace Amarok
{
	[TestFixture]
	public class Test_CompletableObject
	{
		private class TestCompletableObject : CompletableObject
		{
			public static readonly String TypeName = typeof(TestCompletableObject).FullName;


			public volatile Int32 OnCompleteCalled;
			public volatile Int32 OnCompletedCalled;
			public volatile Boolean OnCompleteArgument;

			public Boolean ThrowInOnComplete;
			public Boolean ThrowInOnCompleted;

			public AutoResetEvent OnCompletedEnterBarrier = new AutoResetEvent(false);
			public AutoResetEvent OnCompletedLeaveSignal = new AutoResetEvent(false);


			public TestCompletableObject()
			{
			}

			public TestCompletableObject(String instanceName)
				: base(instanceName)
			{
			}

			public String TestObjectId
			{
				get
				{
					return base.ObjectId;
				}
			}

			public void TestSetInstanceName(String instanceName)
			{
				base.SetInstanceName(instanceName);
			}


			protected override Task OnComplete(Boolean discardAvailableMessages)
			{
				var baseResult = base.OnComplete(discardAvailableMessages);

				OnCompleteArgument = discardAvailableMessages;
				OnCompleteCalled++;

				if (ThrowInOnCompleted)
					throw new ApplicationException("ONCOMPLETE");

				return baseResult;
			}

			protected override void OnCompleted()
			{
				OnCompletedEnterBarrier.WaitOne(5000);

				base.OnCompleted();

				OnCompletedCalled++;

				OnCompletedLeaveSignal.Set();

				if (ThrowInOnCompleted)
					throw new ApplicationException("ONCOMPLETED");
			}


			public Boolean TestIsCompleting
			{
				get
				{
					return base.IsCompleting;
				}
			}

			public Boolean TestIsCompleted
			{
				get
				{
					return base.IsCompleted;
				}
			}

			public Task TestCompletion
			{
				get
				{
					return base.Completion;
				}
			}


			public void TestCompleteObject(Boolean argument)
			{
				base.CompleteObject(argument);
			}

			public void TestDisposeObject()
			{
				base.DisposeObject();
			}
		}


		// --- TESTS ---


		[Test]
		public void Test_Construction()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			Assert.That(obj.TestIsCompleting, Is.False);
			Assert.That(obj.TestIsCompleted, Is.False);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.False);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(0));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
			Assert.That(obj.TestObjectId, Is.EqualTo(TestCompletableObject.TypeName));

			obj.TestSetInstanceName("COM1");

			Assert.That(obj.TestObjectId, Is.EqualTo(TestCompletableObject.TypeName + ":COM1"));
		}

		[Test]
		public void Test_Construction_InstanceName()
		{
			var obj = new TestCompletableObject("BLAH");
			var objInterface = (ICompletable)obj;

			Assert.That(obj.TestIsCompleting, Is.False);
			Assert.That(obj.TestIsCompleted, Is.False);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.False);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(0));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
			Assert.That(obj.TestObjectId, Is.EqualTo(TestCompletableObject.TypeName + ":BLAH"));

			obj.TestSetInstanceName(null);
			Assert.That(obj.TestObjectId, Is.EqualTo(TestCompletableObject.TypeName));
		}


		[Test]
		public void Test_Complete()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			objInterface.Complete();

			Thread.Sleep(500);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(obj.OnCompleteArgument, Is.False);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));

			obj.OnCompletedEnterBarrier.Set();
			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.False);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

		[Test]
		public void Test_Complete_Exceptions()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			obj.ThrowInOnComplete = true;
			obj.ThrowInOnCompleted = true;

			objInterface.Complete();

			Thread.Sleep(500);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));

			obj.OnCompletedEnterBarrier.Set();
			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.False);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

		[Test]
		public void Test_Complete_False()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			objInterface.Complete(false);

			Thread.Sleep(500);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(obj.OnCompleteArgument, Is.False);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));

			obj.OnCompletedEnterBarrier.Set();
			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.False);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

		[Test]
		public void Test_Complete_True()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			objInterface.Complete(true);

			Thread.Sleep(500);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(obj.OnCompleteArgument, Is.True);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));

			obj.OnCompletedEnterBarrier.Set();
			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.True);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

		[Test]
		public void Test_Dispose()
		{
			var obj = new TestCompletableObject();
			var objInterface = (IDisposable)obj;

			obj.OnCompletedEnterBarrier.Set();

			objInterface.Dispose();

			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.True);
		}

		[Test]
		public void Test_Dispose_Exceptions()
		{
			var obj = new TestCompletableObject();
			var objInterface = (IDisposable)obj;

			obj.ThrowInOnComplete = true;
			obj.ThrowInOnCompleted = true;

			obj.OnCompletedEnterBarrier.Set();

			objInterface.Dispose();

			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.True);
		}

		[Test]
		public void Test_CompleteObject_True()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			obj.TestCompleteObject(true);

			Thread.Sleep(500);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(0));
			Assert.That(obj.OnCompleteArgument, Is.True);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));

			obj.OnCompletedEnterBarrier.Set();
			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.True);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

		[Test]
		public void Test_DisposeObject()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			obj.OnCompletedEnterBarrier.Set();

			obj.TestDisposeObject();

			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.True);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

		[Test]
		public void Test_Complete_ConcurrentCalls()
		{
			var obj = new TestCompletableObject();
			var objInterface = (ICompletable)obj;

			obj.OnCompletedEnterBarrier.Set();

			objInterface.Complete(true);

			for (Int32 i = 0; i < 100; i++)
				TaskHelper.Run(() => objInterface.Complete(false));

			objInterface.Completion.Wait();

			obj.OnCompletedLeaveSignal.WaitOne(5000);

			Assert.That(obj.TestIsCompleting, Is.True);
			Assert.That(obj.TestIsCompleted, Is.True);
			Assert.That(obj.TestCompletion, Is.Not.Null);
			Assert.That(obj.TestCompletion.IsCanceled, Is.False);
			Assert.That(obj.TestCompletion.IsCompleted, Is.True);
			Assert.That(obj.TestCompletion.IsFaulted, Is.False);
			Assert.That(obj.OnCompleteCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompletedCalled, Is.EqualTo(1));
			Assert.That(obj.OnCompleteArgument, Is.True);
			Assert.That(objInterface.Completion, Is.SameAs(obj.TestCompletion));
		}

	}
}

#endif
